﻿// AmiMessage.cs
// Copyright © 2013 Alex Forster

namespace Ami
{
	using System;
	using System.Collections;
	using System.Collections.Generic;
	using System.Diagnostics.Contracts;
	using System.IO;

	public delegate AmiMessage MessageConverter( AmiMessage message );

	public class AmiMessage: IEnumerable<KeyValuePair<String, String>>
	{
		protected internal Dictionary<String, String> Headers;

		public AmiMessage()
		{
			this.Headers = new Dictionary<String, String>( StringComparer.OrdinalIgnoreCase );
		}

		public String this[String key]
		{
			get
			{
				String result;
				return this.Headers.TryGetValue( key, out result ) ? result : null;
			}
			protected set
			{
				if( value == null )
				{
					throw new ArgumentNullException( "value" );
				}

				if( this.Headers.ContainsKey( key ) )
				{
					throw new InvalidOperationException( "The header '" + key + "' has already been set" );
				}

				this.Headers.Add( key, value );
			}
		}

		public IEnumerator<KeyValuePair<String, String>> GetEnumerator()
		{
			return this.Headers.GetEnumerator();
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return this.GetEnumerator();
		}

		public void Add( String key, String value )
		{
			Contract.Assert( key != null );

			Contract.Assert( value != null );

			this[key] = value;
		}

		public DateTime Received
		{
			get;
			protected set;
		}

		public static MessageConverter Converter = m => null;

		public static Byte[] Serialize( AmiMessage message )
		{
			Contract.Assert( message != null );

			var bufStream = new MemoryStream();

			using( var bufWriter = new StreamWriter( bufStream ) )
			{
				foreach( var header in message.Headers )
				{
					bufWriter.Write( header.Key.Trim() + ": " + header.Value.Trim() + "\r\n" );
				}

				bufWriter.Write( "\r\n" );
			}

			return bufStream.ToArray();
		}

		public static AmiMessage Deserialize( Byte[] buf, out AmiException error )
		{
			return AmiMessage.Deserialize( buf, DateTime.UtcNow, out error );
		}

		public static AmiMessage Deserialize( Byte[] buf, DateTime timestamp, out AmiException error )
		{
			Contract.Assert( buf != null );

			var result = new AmiMessage();

			result.Received = timestamp;

			error = null;

			var bufStream = new MemoryStream( buf );

			var bufReader = new StreamReader( bufStream );

			for( var cLine = 1;; cLine++ )
			{
				var line = bufReader.ReadLine();

				if( line == null )
				{
					error = new AmiException( "Unexpected end of message", buf );
					break;
				}

				if( line.Equals( String.Empty ) )
				{
					break;
				}

				var kvp = line.Split( new[] { ':' }, 2 );

				if( kvp.Length != 2 )
				{
					error = new AmiException( "Malformed header", cLine, buf );
					break;
				}

				var key = kvp[0].Trim();
				var value = kvp[1].Trim();

				try
				{
					result[key] = value;
				}
				catch( InvalidOperationException )
				{
					error = new AmiException( "Duplicate header '" + key + "'", cLine, buf );
					break;
				}
			}

			return result;
		}
	}
}
